namespace mecs {

    /** 传入array对象后，会自动将其分出hash数组，省去手动操作 */
    export class HashList<T>{
        /** 
         * 用于存放对象的主key对应的值，在_arr中的index,每次更新arr后会重新设置key对应的index
         * eg: 
         * 主key，也就是_keyName为id
         * T:{id:number,name:string}
         * _hash:{1:0,2:1};
         * _getKey({id:1:name:'aa'}) 返回值为 0
         */
        protected _hash: { [key: string]: number };
        protected _arr: T[];
        protected _keyName: string | string[];// 当前hashList中，所存对象的默认主key

        /** 初始化可以用：new HashList([], 'frameId'); 这种格式 */
        constructor(arr: T[], keyName: string | string[]) {
            this._hash = {};
            this._arr = arr;
            this._keyName = keyName;
            this._setHash(arr);
        }

        protected _setHash(arr: T[]) {
            for (let i = 0; i < arr.length; i++) {
                let temp = arr[i];
                let key = this._getKey(temp)
                this._hash[key] = i;
            }
        }

        /** 通过主key，获得此对象在此key上的值 */
        protected _getKey(d: T) {
            if (this._keyName) {
                if (typeof this._keyName == "string") {
                    return d[this._keyName];
                } else {
                    let realKey = d[this._keyName[0]];
                    for (let i = 1; i < this._keyName.length; i++) {
                        realKey += "_" + d[this._keyName[i]];
                    }
                    return realKey;
                }
            }
            return 'ErrorKey';
        }

        getData(key: any, ...arg): T {
            let realKey = this._getRealKey(key, ...arg);
            return this.getDataByKey(realKey)
        }

        getDataByKey(key: any): T {
            let idx = this.mGetDataIdx(key);
            if (idx >= 0) {
                return this._arr[idx];
            }
            return null;
        }

        protected mGetDataIdx(key: any): number {
            let idx = this._hash[key];
            return idx;
        }

        findOne(condition: WhereOption<T>) {
            for (var i = 0, len = this._arr.length; i < len; i++) {
                if (this._checkCondition(this._arr[i], condition)) return this._arr[i];
            }
            return null;
        }

        find(condition: WhereOption<T>) {
            return this._arr.filter((v) => {
                return this._checkCondition(v, condition);
            });
        }

        findOneByMethod(method: compareFunc<T>) {
            for (var i = 0, len = this._arr.length; i < len; i++) {
                if (method(this._arr[i])) return this._arr[i];
            }
            return null;
        }

        findByMethod(method: compareFunc<T>) {
            return this._arr.filter((v) => {
                return method(v);
            });
        }

        private _checkCondition(d: T, condition?: WhereOption<T>): boolean {
            for (var key in condition) {
                let child = condition[key];
                if (child instanceof Array) {
                    if ((<Array<any>>child).indexOf(d[key]) == -1) return false;
                } else {
                    let type = typeof child;
                    if (type == "number" || type == "string" || type == "boolean") {
                        if (child != d[key]) return false;
                    } else {
                        let ops = Object.getOwnPropertySymbols(child);
                        for (let i = 0, len = ops.length; i < len; i++) {
                            if (!OpFunc.inst.check(ops[i], d[key], child[ops[i]])) return false;
                        }
                    }
                };
            }
            return true;
        }

        forEach(method: (item: T, i?: number) => void) {
            for (let i = 0, len = this.length; i < len; i++) {
                method(this._arr[i], i);
            }
        }

        protected _getRealKey(key: any, ...arg): string {
            if (!arg || arg.length == 0) {
                return key;
            }
            let realKey = key;
            for (let i = 0; i < arg.length; i++) {
                realKey += "_" + arg[i];
            }
            return realKey;
        }

        addData(key: any, data: T, ...arg): boolean {
            let realKey = this._getRealKey(key, ...arg);
            let idx = this.mGetDataIdx(realKey);
            if (idx >= 0) {
                this._hash[realKey] = idx;
                this._arr[idx] = data;
                return false;
            } else {
                this._arr.push(data);
                this._hash[realKey] = this._arr.length - 1;
                return true;
            }
        }

        removeData(key: any, ...arg) {
            let realKey = this._getRealKey(key, ...arg);
            let idx = this.mGetDataIdx(realKey);
            let removed: T;
            if (idx >= 0) {
                removed = this._arr.splice(idx, 1)[0];
                for (let i = idx; i < this._arr.length; i++) {
                    let temp = this._arr[i];
                    let key = this._getKey(temp)
                    this._hash[key] -= 1;
                }
            }
            delete this._hash[realKey];
            return removed;
        }

        shiftData() {
            if (this._arr.length) {
                let temp = this._arr.shift();
                let key = this._getKey(temp)
                delete this._hash[key];
                return temp
            }
            return null;
        }

        get arr(): T[] {
            return this._arr;
        }

        get hash() {
            return this._hash;
        }

        get length() {
            return this._arr.length;
        }

        dispose() {
            this._arr = null;
            this._hash = null;
        }
    }

    export type WhereOption<T> = { [P in keyof T]?: WhereValue };
    export type WhereValue = number | string | boolean | Array<number | string | boolean> | WhereOperators;
    export type compareFunc<T> = (item: T) => boolean;
}